{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# `Nuqleon.IO.StreamSegment`\n",
        "\n",
        "Provides a type providing a view into a `System.IO.Stream`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Reference the library"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 1 - Use a local build\n",
        "\n",
        "If you have built the library locally, run the following cell to load the latest build."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"bin/Debug/net6.0/Nuqleon.IO.StreamSegment.dll\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 2 - Use NuGet packages\n",
        "\n",
        "If you want to use the latest published package from NuGet, run the following cell."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"nuget:Nuqleon.IO.StreamSegment,*-*\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## (Optional) Attach a debugger\n",
        "\n",
        "If you'd like to step through the source code of the library while running samples, run the following cell, and follow instructions to start a debugger (e.g. Visual Studio). Navigate to the source code of the library to set breakpoints."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "System.Diagnostics.Debugger.Launch();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## `StreamSegment`\n",
        "\n",
        "A `StreamSegment` implements `Stream` by providing a view over an underlying `Stream`, starting at a specified offset, and with a specified length. An example is shown below, based on the idea of a length-prefixed encoding for sections of data, referred to as chunks."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "First, let's create a function to create a stream that holds a number of chunks. Our goal is to use `StreamSegment` to reconstruct the chunks later."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "using System.IO;\n",
        "\n",
        "static Stream GetStream()\n",
        "{\n",
        "    var data = new MemoryStream();\n",
        "\n",
        "    var chunks = new byte[][]\n",
        "    {\n",
        "        new byte[7] { 2, 3, 5, 11, 13, 17, 19 },\n",
        "        new byte[3] { 23, 29, 31 },\n",
        "    };\n",
        "\n",
        "    using (var writer = new BinaryWriter(data, Encoding.UTF8, leaveOpen: true))\n",
        "    {\n",
        "        foreach (var chunk in chunks)\n",
        "        {\n",
        "            writer.Write(chunk.Length);\n",
        "            writer.Write(chunk);\n",
        "        }\n",
        "    }\n",
        "\n",
        "    data.Position = 0;\n",
        "\n",
        "    return data;\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The resulting stream contains data like this:\n",
        "\n",
        "```\n",
        " 07 00 00 00 02 03 05 0B 0D 11 13 03 00 00 00 17 1D 1F\n",
        " <---------> <------------------> <---------> <------>\n",
        "```\n",
        "\n",
        "where the two chunks are prefixed by the length. Let's print this to confirm the contents of the stream below."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "07 00 00 00 02 03 05 0B 0D 11 13 03 00 00 00 17 1D 1F \r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "var ms = GetStream();\n",
        "\n",
        "var sb = new StringBuilder();\n",
        "\n",
        "using (var reader = new BinaryReader(ms))\n",
        "{\n",
        "    int b;\n",
        "    while ((b = reader.Read()) >= 0)\n",
        "    {\n",
        "        sb.Append($\"{b:X2} \");\n",
        "    }\n",
        "\n",
        "    Console.WriteLine(sb.ToString());\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Now, let's implement the reader to reconstruct the chunks. In here, we'll read the length prefix from the original stream, followed by constructing a `StreamSegment` based on the retrieved length. We can then safely hand out this `Stream` instance to another utility (e.g. a deserializer) without running the risk of it reading out of bounds.\n",
        "\n",
        "First, we'll create the utility that reads a chunk using a `Stream`-based input. In more realistic scenarios, this may invoke a deserializer."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "static byte[] GetChunk(Stream stream)\n",
        "{\n",
        "    var res = new byte[stream.Length];\n",
        "    stream.Read(res, 0, res.Length);\n",
        "    return res;\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We're now ready to read the chunks from the original stream using `StreamSegment`s over the chunks."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "2, 3, 5, 11, 13, 17, 19\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "23, 29, 31\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "var ms = GetStream();\n",
        "\n",
        "var chunks = new List<byte[]>();\n",
        "\n",
        "using (var reader = new BinaryReader(ms))\n",
        "{\n",
        "    while (ms.Position < ms.Length)\n",
        "    {\n",
        "        // Read the length prefix.\n",
        "        var length = reader.ReadInt32();\n",
        "\n",
        "        // Construct a view into the underlying stream.\n",
        "        using (var segment = new StreamSegment(ms, ms.Position, length))\n",
        "        {\n",
        "            chunks.Add(GetChunk(segment));\n",
        "        }\n",
        "    }\n",
        "}\n",
        "\n",
        "foreach (var chunk in chunks)\n",
        "{\n",
        "    Console.WriteLine(string.Join(\", \", chunk));\n",
        "}"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": ".NET (C#)",
      "language": "C#",
      "name": ".net-csharp"
    },
    "language_info": {
      "file_extension": ".cs",
      "mimetype": "text/x-csharp",
      "name": "C#",
      "pygments_lexer": "csharp",
      "version": "9.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}